package space.edge.d.demo.qrcode.privatecode.decoder;

import android.util.Log;

import space.edge.d.demo.qrcode.common.BitArray;
import space.edge.d.demo.qrcode.common.BitMatrix;
import space.edge.d.demo.qrcode.privatecode.common.Point;
import space.edge.d.demo.qrcode.privatecode.encoder.Code;
import space.edge.d.demo.qrcode.exception.FormatException;
import space.edge.d.demo.qrcode.privatecode.ui.CodeRenderer;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by gsl on 2017/10/16.
 */

public class BitMatrixParser {

    private final BitMatrix bitMatrix;
    private Version parsedVersion;
    private FormatInformation parsedFormatInfo;
    private BitArray parsedBits;

    BitMatrixParser(BitMatrix bitMatrix) {
        this.bitMatrix = bitMatrix;
        parseMatrix2();
    }

    private BitArray parseMatrix() {
        int turns = CodeRenderer.NUMBER_OF_TURNS;
        int radius = CodeRenderer.INIT_RADIUS;
        BitArray rowBits = new BitArray();
        BitArray bits;
        for (int i = 0; i < turns; i++) {
            bits = pickPix(radius, bitMatrix);
            // TODO: 2017/10/18
            BitArray rawData = pixToRawData2(bits, i);
            rowBits.appendBitArray(rawData);
            radius += 20;
        }
        parsedBits = rowBits;
        return rowBits;
    }

    /**
     * 一圈像素转为BitArray
     */
    private BitArray pixToRawData(BitArray pixes, int turnIndex) {
        int dataPointCount = CodeRenderer.PER_CIRCLE_POINT_COUNT[turnIndex];
        float dataPointDiameter = pixes.getSize() * 1.0f / dataPointCount;
        BitArray bits = new BitArray();
        int prePixSegment = 0, currentPixSegment = 1 + Math.round(dataPointDiameter) / 2;
        boolean isBlackPix = pixes.get(0);
        for (int i = 1; i < pixes.getSize(); i++) {
            boolean pix = pixes.get(i);
            if (pix == isBlackPix) {
                currentPixSegment++;
            } else {
                if (isNoiseSegment(currentPixSegment, dataPointDiameter)) {
                    currentPixSegment += prePixSegment;
                    prePixSegment = 0;
                    continue;
                }
                float remainder = 0;
                if (prePixSegment > 0)
                    remainder = appendDataSegment(prePixSegment, dataPointDiameter, bits, !isBlackPix);
                prePixSegment = (int) (currentPixSegment + remainder);
                currentPixSegment = 1;
                isBlackPix = pix;
            }
        }
        appendDataSegment(prePixSegment, dataPointDiameter, bits, !isBlackPix);
        currentPixSegment -= dataPointDiameter / 2;
        if (!isNoiseSegment(currentPixSegment, dataPointDiameter))
            appendDataSegment(currentPixSegment, dataPointDiameter, bits, isBlackPix);
        return bits;
    }

    /*---------------- ---------------- 第二种读取数据的方式 ---------------- ----------------*/
    // 求每个数据点的平均数，然后以平均点数为单位，计数黑白点数，最终判断应该是黑点还是白点
    // 这种方式鲁棒性不好
    private BitArray pixToRawData2(BitArray pixes, int turnIndex) {
        int dataPointCount = CodeRenderer.PER_CIRCLE_POINT_COUNT[turnIndex];
        float dataPointDiameter = pixes.getSize() * 1.0f / dataPointCount;
        BitArray bits = new BitArray();
        for (int i = 0; i < dataPointCount; i++) {
            int startIndex = Math.round(i * dataPointDiameter - dataPointDiameter / 2);
            int endIndex = Math.round((i + 1) * dataPointDiameter - dataPointDiameter / 2);
            bits.appendBit(readAPoint(startIndex, endIndex, pixes));
        }
        return removeExtraPixIfNeeded(turnIndex, bits);
    }

    private boolean readAPoint(int startIndex, int endIndex, BitArray pixes) {
        int blackPixCount = 0, whitePixCount = 0;
        for (int i = startIndex; i < endIndex; i++) {
            int index = i;
            if (index < 0) index += pixes.getSize();
            if (pixes.get(index))
                blackPixCount++;
            else
                whitePixCount++;
        }
        return blackPixCount > (whitePixCount - 2);
    }


    /**
     * 如果一段连续数据，长度不足一个数据点的直径的2/3的话，就认为他是噪点
     */
    private boolean isNoiseSegment(float segment, float pointDiameter) {
        return segment < 3.0 * pointDiameter / 5;
    }

    private float appendDataSegment(int segment, float pointDiameter, BitArray bits, boolean value) {
        if (!value) segment -= 2;
        float remainder = segment % pointDiameter;
        int dataCount = Math.round(segment / pointDiameter);
        if (!isNoiseSegment(remainder, pointDiameter)) {
            dataCount++;
            remainder -= pointDiameter;
        }
        bits.appendBits(value ? (2 << dataCount) - 1 : 0, dataCount);
        return remainder;
    }

    /**
     * 删除定位符所占的位数
     */
    private BitArray removeExtraPixIfNeeded(int turnIndex, BitArray bitArray) {
        if (turnIndex < 4)
            return bitArray;
        int[] skippedPoints = CodeRenderer.SKIPPED_POINT[turnIndex - 4];
        BitArray newBitArray = new BitArray();
        int start, end;
        for (int i = 0; i <= skippedPoints.length; i += 2) {
            start = (i - 1) < 0 ? 0 : skippedPoints[i - 1];
            end = i == skippedPoints.length ? bitArray.getSize() : skippedPoints[i];
            for (int j = start; j < end; j++) {
                newBitArray.appendBit(bitArray.get(j));
            }
        }
        return newBitArray;
    }

    /**
     * 提取bitMatrix中一圈像素
     */
    private BitArray pickPix(int radius, BitMatrix bitMatrix) {
        BitArray bits = new BitArray();
        int centerX = bitMatrix.getWidth() / 2;
        int centerY = bitMatrix.getHeight() / 2;
        List<Point> points = bresenhamCircle(centerX, centerY, radius, bitMatrix, bits);
        int segSize = bits.getSize() / 8;
        BitArray finalBits = new BitArray();
        for (int i = 0; i < 8; i++) {
            if (i % 2 == 0) {
                for (int j = 0; j < segSize; j++) {
                    finalBits.appendBit(bits.get(i + j * 8));
                }
            } else {
                for (int j = segSize - 1; j >= 0; j--) {
                    finalBits.appendBit(bits.get(i + j * 8));
                }
            }
        }
        return finalBits;
    }

    /**
     * bresenham画圆算法
     */
    private List<Point> bresenhamCircle(int centerX, int centerY, int raidus, BitMatrix bitMatrix, BitArray bitArray) {
        List<Point> points = new ArrayList<>();
        int x = 0;
        int y = raidus;
        int d = 3 - 2 * raidus;
        circlePlot(centerX, centerY, x, y, bitMatrix, bitArray, points);
        while (x <= y) {
            if (d < 0) {
                d = d + 4 * x + 6;
            } else {
                d = d + 4 * (x - y) + 10;
                y--;
            }
            x++;
            circlePlot(centerX, centerY, x, y, bitMatrix, bitArray, points);
        }
        return points;
    }

    // FIXME: 2017/10/19 后期删掉
    int count = 1;

    private void circlePlot(int centerX, int centerY, int x, int y, BitMatrix bitMatrix, BitArray bits, List<Point> points) {
        bits.appendBit(bitMatrix.get(centerX + x, centerY - y));
        bits.appendBit(bitMatrix.get(centerX + y, centerY - x));
        bits.appendBit(bitMatrix.get(centerX + y, centerY + x));
        bits.appendBit(bitMatrix.get(centerX + x, centerY + y));
        bits.appendBit(bitMatrix.get(centerX - x, centerY + y));
        bits.appendBit(bitMatrix.get(centerX - y, centerY + x));
        bits.appendBit(bitMatrix.get(centerX - y, centerY - x));
        bits.appendBit(bitMatrix.get(centerX - x, centerY - y));

        points.add(new Point(centerX + x, centerY - y));
        points.add(new Point(centerX + y, centerY - x));
        points.add(new Point(centerX + y, centerY + x));
        points.add(new Point(centerX + x, centerY + y));
        points.add(new Point(centerX - x, centerY + y));
        points.add(new Point(centerX - y, centerY + x));
        points.add(new Point(centerX - y, centerY - x));
        points.add(new Point(centerX - x, centerY - y));

        Log.d("wlp", count++ + "（" + (centerX + x) + "，" + (centerY - y) + "）");
        Log.d("wlp", count++ + "（" + (centerX + y) + "，" + (centerY - x) + "）");
        Log.d("wlp", count++ + "（" + (centerX + y) + "，" + (centerY + x) + "）");
        Log.d("wlp", count++ + "（" + (centerX + x) + "，" + (centerY + y) + "）");
        Log.d("wlp", count++ + "（" + (centerX - x) + "，" + (centerY + y) + "）");
        Log.d("wlp", count++ + "（" + (centerX - y) + "，" + (centerY + x) + "）");
        Log.d("wlp", count++ + "（" + (centerX - y) + "，" + (centerY - x) + "）");
        Log.d("wlp", count++ + "（" + (centerX - x) + "，" + (centerY - y) + "）");
    }

    /*---------------- ---------------- 第三种方式 ---------------- ----------------*/
    // 计算出每个数据点的中心坐标，然后计数该中心点，以及8邻域内黑白点个数
    // 这种方式计算出来的结果准确率很高，所以后续采用这种方式

    public BitArray parseMatrix2() {
        int turns = CodeRenderer.NUMBER_OF_TURNS;
        int radius = CodeRenderer.INIT_RADIUS;
        BitArray rowBits = new BitArray();
        for (int i = 0; i < turns; i++) {
            // TODO: 2017/10/18
            BitArray rawData = pixToRawData3(i, radius);
            rowBits.appendBitArray(rawData);
            radius += 20;
        }
        parsedBits = rowBits;
        return rowBits;
    }

    private BitArray pixToRawData3(int turnIndex, int radius) {
        int dataPointCount = CodeRenderer.PER_CIRCLE_POINT_COUNT[turnIndex];
        BitArray result = new BitArray();
        for (int i = 0; i < dataPointCount; i++) {
            if (needSkipPoint(turnIndex, i))
                continue;
            Point pix = calculatePointPosition(i, dataPointCount, radius);
            result.appendBit(readAPoint(pix));
        }
        return result;
    }

    private boolean readAPoint(Point point) {
        boolean center = bitMatrix.get(point.x, point.y);
        int blackCount = 0, whiteCount = 0;
        for (int i = point.x - 1; i <= point.x + 1; i++) {
            for (int j = point.y - 1; j <= point.y + 1; j++) {
                if (bitMatrix.get(i, j))
                    blackCount++;
                else
                    whiteCount++;
            }
        }
        if (center) {
            return blackCount > 5;
        } else
            return whiteCount < 5;
    }

    private Point calculatePointPosition(int currPointIndex, int totalPoints, int radius) {
        double angle = currPointIndex * 2 * Math.PI / totalPoints;
        double sin = Math.sin(angle);
        double cos = Math.cos(angle);
        Point pCenter = new Point();
        pCenter.x = (int) (300 + radius * sin);
        pCenter.y = (int) (300 - radius * cos);
        return pCenter;
    }

    private boolean needSkipPoint(int circleIndex, int pointIndex) {
        if (circleIndex < 4)
            return false;
        switch (circleIndex) {
            case 4:
                return pointIndex >= 43 && pointIndex < 50;
            case 5:
                return (pointIndex >= 46 && pointIndex < 55) || (pointIndex >= 14 && pointIndex < 20) || (pointIndex >= 80 && pointIndex < 87) || (pointIndex >= 114 && pointIndex < 120);
            case 6:
                return (pointIndex >= 49 && pointIndex < 59) || (pointIndex >= 15 && pointIndex < 22) || (pointIndex >= 86 && pointIndex < 93) || (pointIndex >= 122 && pointIndex < 129);
            case 7:
                return (pointIndex >= 53 && pointIndex < 64) || (pointIndex >= 16 && pointIndex < 23) || (pointIndex >= 93 && pointIndex < 100) || (pointIndex >= 132 && pointIndex < 138);
            default:
                return false;
        }
    }

    private int copyBit(int index, int value) {
        value = parsedBits.get(index) ? value << 1 | 0x01 : value << 1;
        return value;
    }

    Version readVersion() throws FormatException {
        if (parsedVersion != null)
            return parsedVersion;
        int versionBits = 0;
        for (int i = 0; i < 18; i++) {
            versionBits = copyBit(i, versionBits);
        }
        Version theParsedVersion = Version.decodeVersionInformation(versionBits);
        if (theParsedVersion != null) {
            parsedVersion = theParsedVersion;
            return theParsedVersion;
        }
        throw FormatException.getFormatInstance();
    }

    FormatInformation readFormatInformation() throws FormatException {
        if (parsedFormatInfo != null)
            return parsedFormatInfo;
        int formatInfoBits = 0;
        for (int i = 18; i < 33; i++) {
            formatInfoBits = copyBit(i, formatInfoBits);
        }
        parsedFormatInfo = FormatInformation.decodeFormatInformation(formatInfoBits);
        if (parsedFormatInfo != null)
            return parsedFormatInfo;
        throw FormatException.getFormatInstance();
    }

    /**
     * 1.先获取版本信息
     * 2.获取ECLevel、掩码号
     * 3.根据掩码号获取掩码运算，去掩码
     */
    byte[] readCodeWords() throws FormatException {
        Version version = readVersion();
        FormatInformation formatInformation = readFormatInformation();

        // TODO: 2017/10/24 获取掩码号，去掩码
        DataMask dataMask = DataMask.values()[formatInformation.getDataMask()];


        byte[] result = new byte[version.getTotalCodewords()];
        int offset = Code.HEADER_LENGTH;
        byte currentByte = 0;
        int bitsRead = 0;
        int resultIndex = 0;
        for (int i = offset; i < result.length * 8 + offset; i++) {
            bitsRead++;
            currentByte <<= 1;
            boolean bit = parsedBits.get(i);
            bit = dataMask.unmaskBit(i - offset, bit);
            if (bit)
                currentByte |= 1;
            if (bitsRead == 8) {
                result[resultIndex++] = currentByte;
                currentByte = 0;
                bitsRead = 0;
            }
        }
        return result;
    }

}
